Completed
Pull Request — master (#99)
by
unknown
03:48
created

APIClient.transaction   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 3
rs 10
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
var useWebWorker = require('./use-webworker')();
22
23
24
/**
25
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
26
 *
27
 * @param promise   {q.Promise}
28
 * @param cb        function
29
 * @return q.Promise
30
 */
31
function callbackify(promise, cb) {
32
    // add a .then to trigger the cb for people using callbacks
33
    if (cb) {
34
        promise
35
            .then(function(res) {
36
                // use q.nextTick for asyncness
37
                q.nextTick(function() {
38
                    cb(null, res);
39
                });
40
            }, function(err) {
41
                // use q.nextTick for asyncness
42
                q.nextTick(function() {
43
                    cb(err, null);
44
                });
45
            });
46
    }
47
48
    // return the promise for people using promises
49
    return promise;
50
}
51
52
/**
53
 * Bindings to consume the BlockTrail API
54
 *
55
 * @param options       object{
56
 *                          apiKey: 'API_KEY',
57
 *                          apiSecret: 'API_SECRET',
58
 *                          host: 'defaults to api.blocktrail.com',
59
 *                          network: 'BTC|LTC',
60
 *                          testnet: true|false
61
 *                      }
62
 * @constructor
63
 */
64
var APIClient = function(options) {
65
    var self = this;
66
67
    // handle constructor call without 'new'
68
    if (!(this instanceof APIClient)) {
69
        return new APIClient(options);
70
    }
71
    self.bitcoinCash = options.network && options.network === "BCC";
72
    self.testnet = options.testnet = options.testnet || false;
73
    if (self.bitcoinCash) {
74
        if (self.testnet) {
75
            self.network = bitcoin.networks.bitcoincashtestnet;
76
        } else {
77
            self.network = bitcoin.networks.bitcoincash;
78
        }
79
    } else {
80
        if (self.testnet) {
81
            self.network = bitcoin.networks.testnet;
82
        } else {
83
            self.network = bitcoin.networks.bitcoin;
84
        }
85
    }
86
87
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
88
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
89
90
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
91
92
    if (typeof options.btccom === "undefined") {
93
        options.btccom = true;
94
    }
95
96
    /**
97
     * @type RestClient
98
     */
99
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
100
    /**
101
     * @type RestClient
102
     */
103
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
104
105
    if (options.btccom) {
106
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
107
    } else {
108
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
109
    }
110
111
};
112
113
APIClient.initRestClient = function(options) {
114
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
115
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
116
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
117
    }
118
119
    // trim off leading https?://
120
    if (options.host && options.host.indexOf("https://") === 0) {
121
        options.https = true;
122
        options.host = options.host.substr(8);
123
    } else if (options.host && options.host.indexOf("http://") === 0) {
124
        options.https = false;
125
        options.host = options.host.substr(7);
126
    }
127
128
    if (typeof options.https === "undefined") {
129
        options.https = true;
130
    }
131
132
    if (!options.port) {
133
        options.port = options.https ? 443 : 80;
134
    }
135
136
    if (options.btccom) {
137
        if (!options.host) {
138
            options.host = 'chain.api.btc.com';
139
        }
140
141
        if (!options.endpoint) {
142
            options.endpoint = "/" + (options.apiVersion || "v3");
143
        }
144
145
    } else {
146
        if (!options.host) {
147
            options.host = 'api.blocktrail.com';
148
        }
149
150
        if (!options.endpoint) {
151
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
152
        }
153
    }
154
155
    return new RestClient(options);
156
};
157
158
var determineDataStorageV2_3 = function(options) {
159
    return q.when(options)
160
        .then(function(options) {
161
            // legacy
162
            if (options.storePrimaryMnemonic) {
163
                options.storeDataOnServer = options.storePrimaryMnemonic;
164
            }
165
166
            // storeDataOnServer=false when primarySeed is provided
167
            if (typeof options.storeDataOnServer === "undefined") {
168
                options.storeDataOnServer = !options.primarySeed;
169
            }
170
171
            return options;
172
        });
173
};
174
175
var produceEncryptedDataV2 = function(options, notify) {
176
    return q.when(options)
177
        .then(function(options) {
178
            if (options.storeDataOnServer) {
179
                if (!options.secret) {
180
                    if (!options.passphrase) {
181
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
182
                    }
183
184
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
185
186
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
187
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
188
                }
189
190
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
191
192
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
193
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
194
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
195
196
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
197
198
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
199
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
200
            }
201
202
            return options;
203
        });
204
};
205
206
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
207
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
208
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
209
        var saltBuf = Encryption.generateSalt();
210
        var iv = Encryption.generateIV();
211
212
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
213
            return require('./webworker');
214
        }, onLoadWorkerLoadAsmCrypto, {
215
            method: 'Encryption.encryptWithSaltAndIV',
216
            pt: pt,
217
            pw: pw,
218
            saltBuf: saltBuf,
219
            iv: iv,
220
            iterations: iter
221
        })
222
            .then(function(data) {
223
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
224
            });
225
    } else {
226
        try {
227
            return q.when(Encryption.encrypt(pt, pw, iter));
228
        } catch (e) {
229
            return q.reject(e);
230
        }
231
    }
232
};
233
234
APIClient.prototype.promisedDecrypt = function(ct, pw) {
235
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
236
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
237
            return require('./webworker');
238
        }, onLoadWorkerLoadAsmCrypto, {
239
            method: 'Encryption.decrypt',
240
            ct: ct,
241
            pw: pw
242
        })
243
            .then(function(data) {
244
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
245
            });
246
    } else {
247
        try {
248
            return q.when(Encryption.decrypt(ct, pw));
249
        } catch (e) {
250
            return q.reject(e);
251
        }
252
    }
253
};
254
255
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
256
    var self = this;
257
258
    return q.when(options)
259
        .then(function(options) {
260
            if (options.storeDataOnServer) {
261
                return q.when()
262
                    .then(function() {
263
                        if (!options.secret) {
264
                            if (!options.passphrase) {
265
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
266
                            }
267
268
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
269
270
                            // -> now a buffer
271
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
272
273
                            // -> now a buffer
274
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
275
                                .then(function(encryptedSecret) {
276
                                    options.encryptedSecret = encryptedSecret;
277
                                });
278
                        } else {
279
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
280
                                throw new Error('Secret must be a buffer');
281
                            }
282
                        }
283
                    })
284
                    .then(function() {
285
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
286
287
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
288
                            .then(function(encryptedPrimarySeed) {
289
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
290
                            });
291
                    })
292
                    .then(function() {
293
                        // skip generating recovery secret when explicitly set to false
294
                        if (options.recoverySecret === false) {
295
                            return;
296
                        }
297
298
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
299
                        if (!options.recoverySecret) {
300
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
301
                        }
302
303
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
304
                            .then(function(recoveryEncryptedSecret) {
305
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
306
                            });
307
                    })
308
                    .then(function() {
309
                        return options;
310
                    });
311
            } else {
312
                return options;
313
            }
314
        });
315
};
316
317
var doRemainingWalletDataV2_3 = function(options, network, notify) {
318
    return q.when(options)
319
        .then(function(options) {
320
            if (!options.backupPublicKey) {
321
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
322
            }
323
324
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
325
326
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
327
328
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
329
330
            if (!options.backupPublicKey) {
331
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
332
                options.backupPublicKey = options.backupPrivateKey.neutered();
333
            }
334
335
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
336
337
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
338
339
            return options;
340
        });
341
};
342
343
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
344
    var self = this;
345
346
    var deferred = q.defer();
347
    deferred.promise.spreadNodeify(cb);
348
349
    deferred.resolve(q.fcall(function() {
350
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
351
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
352
        });
353
    }));
354
355
    return deferred.promise;
356
};
357
358
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
359
    var self = this;
360
361
    if (useWebWorker) {
362
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
363
            return require('./webworker');
364
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
365
            .then(function(data) {
366
                return data.seed;
367
            });
368
    } else {
369
        try {
370
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
371
        } catch (e) {
372
            return q.reject(e);
373
        }
374
    }
375
};
376
377
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
378
    var self = this;
379
380
    var deferred = q.defer();
381
    deferred.promise.nodeify(cb);
382
383
    try {
384
        // avoid conflicting options
385
        if (options.passphrase && options.password) {
386
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
387
        }
388
        // normalize passphrase/password
389
        options.passphrase = options.passphrase || options.password;
390
        delete options.password;
391
392
        // avoid conflicting options
393
        if (options.primaryMnemonic && options.primarySeed) {
394
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
395
        }
396
397
        // avoid deprecated options
398
        if (options.primaryPrivateKey) {
399
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
400
        }
401
402
        // make sure we have at least one thing to use
403
        if (!options.primaryMnemonic && !options.primarySeed) {
404
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
405
        }
406
407
        if (options.primarySeed) {
408
            self.primarySeed = options.primarySeed;
409
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
410
            deferred.resolve(options);
411
        } else {
412
            if (!options.passphrase) {
413
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
414
            }
415
416
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
417
                .then(function(seedHex) {
418
                    try {
419
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
420
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
421
                        deferred.resolve(options);
422
                    } catch (e) {
423
                        deferred.reject(e);
424
                    }
425
                }, function(e) {
426
                    deferred.reject(e);
427
                });
428
        }
429
    } catch (e) {
430
        deferred.reject(e);
431
    }
432
433
    return deferred.promise;
434
};
435
436
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
437
    var self = this;
438
439
    var deferred = q.defer();
440
    deferred.promise.nodeify(cb);
441
442
    try {
443
        // avoid conflicting options
444
        if (options.backupMnemonic && options.backupPublicKey) {
445
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
446
        }
447
448
        // make sure we have at least one thing to use
449
        if (!options.backupMnemonic && !options.backupPublicKey) {
450
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
451
        }
452
453
        if (options.backupPublicKey) {
454
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
455
                deferred.resolve(options);
456
            } else {
457
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
458
                deferred.resolve(options);
459
            }
460
        } else {
461
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
462
                options.backupPublicKey = backupPrivateKey.neutered();
463
                deferred.resolve(options);
464
            }, function(e) {
465
                deferred.reject(e);
466
            });
467
        }
468
    } catch (e) {
469
        deferred.reject(e);
470
    }
471
472
    return deferred.promise;
473
};
474
475
APIClient.prototype.debugAuth = function(cb) {
476
    var self = this;
477
478
    return self.dataClient.get("/debug/http-signature", null, true, cb);
479
};
480
481
/**
482
 * get a single address
483
 *
484
 * @param address      string       address hash
485
 * @param [cb]          function    callback function to call when request is complete
486
 * @return q.Promise
487
 */
488
APIClient.prototype.address = function(address, cb) {
489
    var self = this;
490
491
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
492
        .then(function(data) {
493
            return self.converter.handleErros(self, data);
494
        })
495
        .then(function(data) {
496
            if (data === null) {
497
                return data;
498
            } else {
499
                return self.converter.convertAddress(data);
500
            }
501
        }), cb);
502
};
503
504
APIClient.prototype.addresses = function(addresses, cb) {
505
    var self = this;
506
507
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
508
};
509
510
511
/**
512
 * get all transactions for an address (paginated)
513
 *
514
 * @param address       string      address hash
515
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
516
 * @param [cb]          function    callback function to call when request is complete
517
 * @return q.Promise
518
 */
519
APIClient.prototype.addressTransactions = function(address, params, cb) {
520
521
    var self = this;
522
523
    if (typeof params === "function") {
524
        cb = params;
525
        params = null;
526
    }
527
528
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
529
        .then(function(data) {
530
            return self.converter.handleErros(self, data);
531
        })
532
        .then(function(data) {
533
            return data.data === null ? data : self.converter.convertAddressTxs(data);
534
        }), cb);
535
};
536
537
/**
538
 * get all transactions for a batch of addresses (paginated)
539
 *
540
 * @param addresses     array       address hashes
541
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
542
 * @param [cb]          function    callback function to call when request is complete
543
 * @return q.Promise
544
 */
545
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
546
    var self = this;
547
548
    if (typeof params === "function") {
549
        cb = params;
550
        params = null;
551
    }
552
553
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
554
};
555
556
/**
557
 * get all unconfirmed transactions for an address (paginated)
558
 *
559
 * @param address       string      address hash
560
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
561
 * @param [cb]          function    callback function to call when request is complete
562
 * @return q.Promise
563
 */
564
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
565
    var self = this;
566
567
    if (typeof params === "function") {
568
        cb = params;
569
        params = null;
570
    }
571
572
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
573
        .then(function(data) {
574
            return self.converter.handleErros(self, data);
575
        })
576
        .then(function(data) {
577
            if (data.data === null) {
578
                return data;
579
            }
580
581
            var res = self.converter.convertAddressTxs(data);
582
            res.data = res.data.filter(function(tx) {
583
                return !tx.confirmations;
584
            });
585
586
            return res;
587
        }), cb);
588
};
589
590
/**
591
 * get all unspent outputs for an address (paginated)
592
 *
593
 * @param address       string      address hash
594
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
595
 * @param [cb]          function    callback function to call when request is complete
596
 * @return q.Promise
597
 */
598
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
599
    var self = this;
600
601
    if (typeof params === "function") {
602
        cb = params;
603
        params = null;
604
    }
605
606
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
607
        .then(function(data) {
608
            return self.converter.handleErros(self, data);
609
        })
610
        .then(function(data) {
611
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
612
        }), cb);
613
};
614
615
/**
616
 * get all unspent outputs for a batch of addresses (paginated)
617
 *
618
 * @param addresses     array       address hashes
619
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
620
 * @param [cb]          function    callback function to call when request is complete
621
 * @return q.Promise
622
 */
623
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
624
    var self = this;
625
626
    if (self.converter instanceof BtccomConverter) {
627
        throw new Error("Not implemented");
628
    }
629
630
    if (typeof params === "function") {
631
        cb = params;
632
        params = null;
633
    }
634
635
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
636
};
637
638
/**
639
 * verify ownership of an address
640
 *
641
 * @param address       string      address hash
642
 * @param signature     string      a signed message (the address hash) using the private key of the address
643
 * @param [cb]          function    callback function to call when request is complete
644
 * @return q.Promise
645
 */
646
APIClient.prototype.verifyAddress = function(address, signature, cb) {
647
    var self = this;
648
649
    return self.verifyMessage(address, address, signature, cb);
650
};
651
652
/**
653
 *
654
 * get all blocks (paginated)
655
 * ASK
656
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
657
 * @param [cb]          function    callback function to call when request is complete
658
 * @return q.Promise
659
 */
660
APIClient.prototype.allBlocks = function(params, cb) {
661
    var self = this;
662
663
    if (self.converter instanceof BtccomConverter) {
664
        throw new Error("Not implemented");
665
    }
666
667
    if (typeof params === "function") {
668
        cb = params;
669
        params = null;
670
    }
671
672
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
673
};
674
675
/**
676
 * get a block
677
 *
678
 * @param block         string|int  a block hash or a block height
679
 * @param [cb]          function    callback function to call when request is complete
680
 * @return q.Promise
681
 */
682
APIClient.prototype.block = function(block, cb) {
683
    var self = this;
684
685
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
686
        .then(function(data) {
687
            return self.converter.handleErros(self, data);
688
        })
689
        .then(function(data) {
690
            return data.data === null ? data : self.converter.convertBlock(data);
691
        }), cb);
692
};
693
694
/**
695
 * get the latest block
696
 *
697
 * @param [cb]          function    callback function to call when request is complete
698
 * @return q.Promise
699
 */
700
APIClient.prototype.blockLatest = function(cb) {
701
    var self = this;
702
703
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
704
        .then(function(data) {
705
            return self.converter.handleErros(self, data);
706
        })
707
        .then(function(data) {
708
            return data.data === null ? data : self.converter.convertBlock(data);
709
        }), cb);
710
};
711
712
/**
713
 * get all transactions for a block (paginated)
714
 *
715
 * @param block         string|int  a block hash or a block height
716
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
717
 * @param [cb]          function    callback function to call when request is complete
718
 * @return q.Promise
719
 */
720
APIClient.prototype.blockTransactions = function(block, params, cb) {
721
    var self = this;
722
723
    if (typeof params === "function") {
724
        cb = params;
725
        params = null;
726
    }
727
728
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
729
        .then(function(data) {
730
            return self.converter.handleErros(self, data);
731
        })
732
        .then(function(data) {
733
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
734
        }), cb);
735
};
736
737
/**
738
 * get a single transaction
739
 *
740
 * @param tx            string      transaction hash
741
 * @param [cb]          function    callback function to call when request is complete
742
 * @return q.Promise
743
 */
744
APIClient.prototype.transaction = function(tx, cb) {
745
    var self = this;
746
747
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
748
        .then(function(data) {
749
            return self.converter.handleErros(self, data);
750
        })
751
        .then(function(data) {
752
            if (data.data === null) {
753
                return data;
754
            } else {
755
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
756
                if (self.converter instanceof BtccomConverter) {
757
                    var txPath = data.data.hash + ".rawhex";
758
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
759
                        .then(function(rawTx) {
760
                            return [data, rawTx];
761
                        })
762
                        .then(function(dataAndTx) {
763
                            if (dataAndTx !== null) {
764
                                var data = dataAndTx[0];
765
                                var rawTx = dataAndTx[1];
766
                                return self.converter.convertTx(data, rawTx);
767
                            } else {
768
                                return dataAndTx;
769
                            }
770
                        });
771
                } else {
772
                    return self.converter.convertTx(data);
773
                }
774
            }
775
        }), cb);
776
};
777
778
/**
779
 * get a batch of transactions
780
 *
781
 * @param txs           string[]    list of transaction hashes (txId)
782
 * @param [cb]          function    callback function to call when request is complete
783
 * @return q.Promise
784
 */
785
APIClient.prototype.transactions = function(txs, cb) {
786
    var self = this;
787
788
    if (self.converter instanceof BtccomConverter) {
789
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
790
            .then(function(data) {
791
                return self.converter.handleErros(self, data);
792
            })
793
            .then(function(data) {
794
                if (data.data === null) {
795
                    return data;
796
                } else {
797
                    return self.converter.convertTxs(data);
798
                }
799
            }), cb);
800
    } else {
801
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
802
    }
803
};
804
805
/**
806
 * get a paginated list of all webhooks associated with the api user
807
 *
808
 * @param [params]      object      pagination: {page: 1, limit: 20}
809
 * @param [cb]          function    callback function to call when request is complete
810
 * @return q.Promise
811
 */
812
APIClient.prototype.allWebhooks = function(params, cb) {
813
    var self = this;
814
815
    if (typeof params === "function") {
816
        cb = params;
817
        params = null;
818
    }
819
820
    return self.blocktrailClient.get("/webhooks", params, cb);
821
};
822
823
/**
824
 * create a new webhook
825
 *
826
 * @param url           string      the url to receive the webhook events
827
 * @param [identifier]  string      a unique identifier associated with the webhook
828
 * @param [cb]          function    callback function to call when request is complete
829
 * @return q.Promise
830
 */
831
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
832
    var self = this;
833
834
    if (typeof identifier === "function") {
835
        //mimic function overloading
836
        cb = identifier;
837
        identifier = null;
838
    }
839
840
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
841
};
842
843
/**
844
 * Converts a cash address to the legacy (base58) format
845
 * @param {string} input
846
 * @returns {string}
847
 */
848
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
849
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
850
        var address;
851
        try {
852
            bitcoin.address.fromBase58Check(input, this.network);
853
            return input;
854
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
855
856
        address = bitcoin.address.fromCashAddress(input, this.network);
857
        var prefix;
858
        if (address.version === bitcoin.script.types.P2PKH) {
859
            prefix = this.network.pubKeyHash;
860
        } else if (address.version === bitcoin.script.types.P2SH) {
861
            prefix = this.network.scriptHash;
862
        } else {
863
            throw new Error("Unsupported address type");
864
        }
865
866
        return bitcoin.address.toBase58Check(address.hash, prefix);
867
    }
868
869
    throw new Error("Cash addresses only work on bitcoin cash");
870
};
871
872
/**
873
 * Converts a legacy bitcoin to the new cashaddr format
874
 * @param {string} input
875
 * @returns {string}
876
 */
877
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
878
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
879
        var address;
880
        try {
881
            bitcoin.address.fromCashAddress(input, this.network);
882
            return input;
883
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
884
885
        address = bitcoin.address.fromBase58Check(input, this.network);
886
        var scriptType;
887
        if (address.version === this.network.pubKeyHash) {
888
            scriptType = bitcoin.script.types.P2PKH;
889
        } else if (address.version === this.network.scriptHash) {
890
            scriptType = bitcoin.script.types.P2SH;
891
        } else {
892
            throw new Error("Unsupported address type");
893
        }
894
895
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
896
    }
897
898
    throw new Error("Cash addresses only work on bitcoin cash");
899
};
900
901
/**
902
 * get an existing webhook by it's identifier
903
 *
904
 * @param identifier    string      the unique identifier of the webhook to get
905
 * @param [cb]          function    callback function to call when request is complete
906
 * @return q.Promise
907
 */
908
APIClient.prototype.getWebhook = function(identifier, cb) {
909
    var self = this;
910
911
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
912
};
913
914
/**
915
 * update an existing webhook
916
 *
917
 * @param identifier    string      the unique identifier of the webhook
918
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
919
 * @param [cb]          function    callback function to call when request is complete
920
 * @return q.Promise
921
 */
922
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
923
    var self = this;
924
925
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
926
};
927
928
/**
929
 * deletes an existing webhook and any event subscriptions associated with it
930
 *
931
 * @param identifier    string      the unique identifier of the webhook
932
 * @param [cb]          function    callback function to call when request is complete
933
 * @return q.Promise
934
 */
935
APIClient.prototype.deleteWebhook = function(identifier, cb) {
936
    var self = this;
937
938
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
939
};
940
941
/**
942
 * get a paginated list of all the events a webhook is subscribed to
943
 *
944
 * @param identifier    string      the unique identifier of the webhook
945
 * @param [params]      object      pagination: {page: 1, limit: 20}
946
 * @param [cb]          function    callback function to call when request is complete
947
 * @return q.Promise
948
 */
949
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
950
    var self = this;
951
952
    if (typeof params === "function") {
953
        cb = params;
954
        params = null;
955
    }
956
957
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
958
};
959
960
/**
961
 * subscribes a webhook to transaction events for a particular transaction
962
 *
963
 * @param identifier    string      the unique identifier of the webhook
964
 * @param transaction   string      the transaction hash
965
 * @param confirmations integer     the amount of confirmations to send
966
 * @param [cb]          function    callback function to call when request is complete
967
 * @return q.Promise
968
 */
969
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
970
    var self = this;
971
    var postData = {
972
        'event_type': 'transaction',
973
        'transaction': transaction,
974
        'confirmations': confirmations
975
    };
976
977
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
978
};
979
980
/**
981
 * subscribes a webhook to transaction events on a particular address
982
 *
983
 * @param identifier    string      the unique identifier of the webhook
984
 * @param address       string      the address hash
985
 * @param confirmations integer     the amount of confirmations to send
986
 * @param [cb]          function    callback function to call when request is complete
987
 * @return q.Promise
988
 */
989
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
990
    var self = this;
991
    var postData = {
992
        'event_type': 'address-transactions',
993
        'address': address,
994
        'confirmations': confirmations
995
    };
996
997
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
998
};
999
1000
/**
1001
 * batch subscribes a webhook to multiple transaction events
1002
 *
1003
 * @param  identifier   string      the unique identifier of the webhook
1004
 * @param  batchData    array       An array of objects containing batch event data:
1005
 *                                  {address : 'address', confirmations : 'confirmations']
1006
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1007
 * @param [cb]          function    callback function to call when request is complete
1008
 * @return q.Promise
1009
 */
1010
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1011
    var self = this;
1012
    batchData.forEach(function(record) {
1013
        record.event_type = 'address-transactions';
1014
    });
1015
1016
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1017
};
1018
1019
/**
1020
 * subscribes a webhook to a new block event
1021
 *
1022
 * @param identifier    string      the unique identifier of the webhook
1023
 * @param [cb]          function    callback function to call when request is complete
1024
 * @return q.Promise
1025
 */
1026
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1027
    var self = this;
1028
    var postData = {
1029
        'event_type': 'block'
1030
    };
1031
1032
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1033
};
1034
1035
/**
1036
 * removes an address transaction event subscription from a webhook
1037
 *
1038
 * @param identifier    string      the unique identifier of the webhook
1039
 * @param address       string      the address hash
1040
 * @param [cb]          function    callback function to call when request is complete
1041
 * @return q.Promise
1042
 */
1043
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1044
    var self = this;
1045
1046
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1047
};
1048
1049
/**
1050
 * removes an transaction event subscription from a webhook
1051
 *
1052
 * @param identifier    string      the unique identifier of the webhook
1053
 * @param transaction   string      the transaction hash
1054
 * @param [cb]          function    callback function to call when request is complete
1055
 * @return q.Promise
1056
 */
1057
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1058
    var self = this;
1059
1060
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1061
};
1062
1063
/**
1064
 * removes a block event subscription from a webhook
1065
 *
1066
 * @param identifier    string      the unique identifier of the webhook
1067
 * @param [cb]          function    callback function to call when request is complete
1068
 * @return q.Promise
1069
 */
1070
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1071
    var self = this;
1072
1073
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1074
};
1075
1076
/**
1077
 * initialize an existing wallet
1078
 *
1079
 * Either takes two argument:
1080
 * @param options       object      {}
1081
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1082
 *
1083
 * Or takes three arguments (old, deprecated syntax):
1084
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1085
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1086
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1081. The second definition is ignored.
Loading history...
1087
 *
1088
 * @returns {q.Promise}
1089
 */
1090
APIClient.prototype.initWallet = function(options, cb) {
1091
    var self = this;
1092
1093
    if (typeof options !== "object") {
1094
        // get the old-style arguments
1095
        options = {
1096
            identifier: arguments[0],
1097
            passphrase: arguments[1]
1098
        };
1099
1100
        cb = arguments[2];
1101
    }
1102
1103
    if (options.check_backup_key) {
1104
        if (typeof options.check_backup_key !== "string") {
1105
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1106
        }
1107
    }
1108
1109
    var deferred = q.defer();
1110
    deferred.promise.spreadNodeify(cb);
1111
1112
    var identifier = options.identifier;
1113
1114
    if (!identifier) {
1115
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1116
        return deferred.promise;
1117
    }
1118
1119
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1120
        var keyIndex = options.keyIndex || result.key_index;
1121
1122
        options.walletVersion = result.wallet_version;
1123
1124
        if (options.check_backup_key) {
1125
            if (options.check_backup_key !== result.backup_public_key[0]) {
1126
                throw new Error("Backup key returned from server didn't match our own copy");
1127
            }
1128
        }
1129
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1130
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1131
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1132
        });
1133
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1134
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1135
        });
1136
1137
        // initialize wallet
1138
        var wallet = new Wallet(
1139
            self,
1140
            identifier,
1141
            options.walletVersion,
1142
            result.primary_mnemonic,
1143
            result.encrypted_primary_seed,
1144
            result.encrypted_secret,
1145
            primaryPublicKeys,
1146
            backupPublicKey,
1147
            blocktrailPublicKeys,
1148
            keyIndex,
1149
            result.segwit || 0,
1150
            self.testnet,
1151
            result.checksum,
1152
            result.upgrade_key_index,
1153
            options.useCashAddress,
1154
            options.bypassNewAddressCheck
1155
        );
1156
1157
        wallet.recoverySecret = result.recovery_secret;
1158
1159
        if (!options.readOnly) {
1160
            return wallet.unlock(options).then(function() {
1161
                return wallet;
1162
            });
1163
        } else {
1164
            return wallet;
1165
        }
1166
    }));
1167
1168
    return deferred.promise;
1169
};
1170
1171
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1172
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1173
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1174
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1175
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1176
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1177
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1178
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1179
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1180
1181
/**
1182
 * create a new wallet
1183
 *   - will generate a new primary seed and backup seed
1184
 *
1185
 * Either takes two argument:
1186
 * @param options       object      {}
1187
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1188
 *
1189
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1190
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1186. The second definition is ignored.
Loading history...
1191
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1187. The second definition is ignored.
Loading history...
1192
 *
1193
 * Or takes four arguments (old, deprecated syntax):
1194
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1195
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1196
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1197
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1187. The second definition is ignored.
Loading history...
1198
 * @returns {q.Promise}
1199
 */
1200
APIClient.prototype.createNewWallet = function(options, cb) {
1201
    /* jshint -W071, -W074 */
1202
1203
    var self = this;
1204
1205
    if (typeof options !== "object") {
1206
        // get the old-style arguments
1207
        var identifier = arguments[0];
1208
        var passphrase = arguments[1];
1209
        var keyIndex = arguments[2];
1210
        cb = arguments[3];
1211
1212
        // keyIndex is optional
1213
        if (typeof keyIndex === "function") {
1214
            cb = keyIndex;
1215
            keyIndex = null;
1216
        }
1217
1218
        options = {
1219
            identifier: identifier,
1220
            passphrase: passphrase,
1221
            keyIndex: keyIndex
1222
        };
1223
    }
1224
1225
    // default to v3
1226
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1227
1228
    var deferred = q.defer();
1229
    deferred.promise.spreadNodeify(cb);
1230
1231
    q.nextTick(function() {
1232
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1233
1234
        options.keyIndex = options.keyIndex || 0;
1235
        options.passphrase = options.passphrase || options.password;
1236
        delete options.password;
1237
1238
        if (!options.identifier) {
1239
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1240
            return deferred.promise;
1241
        }
1242
1243
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1244
            self._createNewWalletV1(options)
1245
                .progress(function(p) { deferred.notify(p); })
1246
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1247
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1248
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1249
            self._createNewWalletV2(options)
1250
                .progress(function(p) { deferred.notify(p); })
1251
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1252
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1253
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1254
            self._createNewWalletV3(options)
1255
                .progress(function(p) { deferred.notify(p); })
1256
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1257
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1258
        } else {
1259
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1260
        }
1261
    });
1262
1263
    return deferred.promise;
1264
};
1265
1266
APIClient.prototype._createNewWalletV1 = function(options) {
1267
    var self = this;
1268
1269
    var deferred = q.defer();
1270
1271
    q.nextTick(function() {
1272
1273
        if (!options.primaryMnemonic && !options.primarySeed) {
1274
            if (!options.passphrase && !options.password) {
1275
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1276
                return deferred.promise;
1277
            } else {
1278
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1279
                if (options.storePrimaryMnemonic !== false) {
1280
                    options.storePrimaryMnemonic = true;
1281
                }
1282
            }
1283
        }
1284
1285
        if (!options.backupMnemonic && !options.backupPublicKey) {
1286
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1287
        }
1288
1289
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1290
1291
        self.resolvePrimaryPrivateKeyFromOptions(options)
1292
            .then(function(options) {
1293
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1294
1295
                return self.resolveBackupPublicKeyFromOptions(options)
1296
                    .then(function(options) {
1297
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1298
1299
                        // create a checksum of our private key which we'll later use to verify we used the right password
1300
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1301
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1302
                        var keyIndex = options.keyIndex;
1303
1304
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1305
1306
                        // send the public keys to the server to store them
1307
                        //  and the mnemonic, which is safe because it's useless without the password
1308
                        return self.storeNewWalletV1(
1309
                            options.identifier,
1310
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1311
                            [options.backupPublicKey.toBase58(), "M"],
1312
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1313
                            checksum,
1314
                            keyIndex,
1315
                            options.segwit || null
1316
                        )
1317
                            .then(function(result) {
1318
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1319
1320
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1321
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1322
                                });
1323
1324
                                var wallet = new Wallet(
1325
                                    self,
1326
                                    options.identifier,
1327
                                    Wallet.WALLET_VERSION_V1,
1328
                                    options.primaryMnemonic,
1329
                                    null,
1330
                                    null,
1331
                                    {keyIndex: primaryPublicKey},
1332
                                    options.backupPublicKey,
1333
                                    blocktrailPublicKeys,
1334
                                    keyIndex,
1335
                                    result.segwit || 0,
1336
                                    self.testnet,
1337
                                    checksum,
1338
                                    result.upgrade_key_index,
1339
                                    options.useCashAddress,
1340
                                    options.bypassNewAddressCheck
1341
                                );
1342
1343
                                return wallet.unlock({
1344
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1345
                                    passphrase: options.passphrase,
1346
                                    primarySeed: options.primarySeed,
1347
                                    primaryMnemonic: null // explicit null
1348
                                }).then(function() {
1349
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1350
                                    return [
1351
                                        wallet,
1352
                                        {
1353
                                            walletVersion: wallet.walletVersion,
1354
                                            primaryMnemonic: options.primaryMnemonic,
1355
                                            backupMnemonic: options.backupMnemonic,
1356
                                            blocktrailPublicKeys: blocktrailPublicKeys
1357
                                        }
1358
                                    ];
1359
                                });
1360
                            });
1361
                    }
1362
                );
1363
            })
1364
            .then(
1365
            function(r) {
1366
                deferred.resolve(r);
1367
            },
1368
            function(e) {
1369
                deferred.reject(e);
1370
            }
1371
        )
1372
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1373
    });
1374
1375
    return deferred.promise;
1376
};
1377
1378 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1379
    var self = this;
1380
1381
    var deferred = q.defer();
1382
1383
    // avoid modifying passed options
1384
    options = _.merge({}, options);
1385
1386
    determineDataStorageV2_3(options)
1387
        .then(function(options) {
1388
            options.passphrase = options.passphrase || options.password;
1389
            delete options.password;
1390
1391
            // avoid deprecated options
1392
            if (options.primaryPrivateKey) {
1393
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1394
            }
1395
1396
            // seed should be provided or generated
1397
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1398
1399
            return options;
1400
        })
1401
        .then(function(options) {
1402
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1403
        })
1404
        .then(function(options) {
1405
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1406
        })
1407
        .then(function(options) {
1408
            // create a checksum of our private key which we'll later use to verify we used the right password
1409
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1410
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1411
            var keyIndex = options.keyIndex;
1412
1413
            // send the public keys and encrypted data to server
1414
            return self.storeNewWalletV2(
1415
                options.identifier,
1416
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1417
                [options.backupPublicKey.toBase58(), "M"],
1418
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1419
                options.storeDataOnServer ? options.encryptedSecret : false,
1420
                options.storeDataOnServer ? options.recoverySecret : false,
1421
                checksum,
1422
                keyIndex,
1423
                options.support_secret || null,
1424
                options.segwit || null
1425
            )
1426
                .then(
1427
                function(result) {
1428
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1429
1430
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1431
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1432
                    });
1433
1434
                    var wallet = new Wallet(
1435
                        self,
1436
                        options.identifier,
1437
                        Wallet.WALLET_VERSION_V2,
1438
                        null,
1439
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1440
                        options.storeDataOnServer ? options.encryptedSecret : null,
1441
                        {keyIndex: options.primaryPublicKey},
1442
                        options.backupPublicKey,
1443
                        blocktrailPublicKeys,
1444
                        keyIndex,
1445
                        result.segwit || 0,
1446
                        self.testnet,
1447
                        checksum,
1448
                        result.upgrade_key_index,
1449
                        options.useCashAddress,
1450
                        options.bypassNewAddressCheck
1451
                    );
1452
1453
                    // pass along decrypted data to avoid extra work
1454
                    return wallet.unlock({
1455
                        walletVersion: Wallet.WALLET_VERSION_V2,
1456
                        passphrase: options.passphrase,
1457
                        primarySeed: options.primarySeed,
1458
                        secret: options.secret
1459
                    }).then(function() {
1460
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1461
                        return [
1462
                            wallet,
1463
                            {
1464
                                walletVersion: wallet.walletVersion,
1465
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1466
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1467
                                    null,
1468
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1469
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1470
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1471
                                    null,
1472
                                encryptedSecret: options.encryptedSecret ?
1473
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1474
                                    null,
1475
                                blocktrailPublicKeys: blocktrailPublicKeys
1476
                            }
1477
                        ];
1478
                    });
1479
                }
1480
            );
1481
        })
1482
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1483
1484
    return deferred.promise;
1485
};
1486
1487 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1488
    var self = this;
1489
1490
    var deferred = q.defer();
1491
1492
    // avoid modifying passed options
1493
    options = _.merge({}, options);
1494
1495
    determineDataStorageV2_3(options)
1496
        .then(function(options) {
1497
            options.passphrase = options.passphrase || options.password;
1498
            delete options.password;
1499
1500
            // avoid deprecated options
1501
            if (options.primaryPrivateKey) {
1502
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1503
            }
1504
1505
            // seed should be provided or generated
1506
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1507
1508
            return options;
1509
        })
1510
        .then(function(options) {
1511
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1512
        })
1513
        .then(function(options) {
1514
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1515
        })
1516
        .then(function(options) {
1517
            // create a checksum of our private key which we'll later use to verify we used the right password
1518
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1519
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1520
            var keyIndex = options.keyIndex;
1521
1522
            // send the public keys and encrypted data to server
1523
            return self.storeNewWalletV3(
1524
                options.identifier,
1525
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1526
                [options.backupPublicKey.toBase58(), "M"],
1527
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1528
                options.storeDataOnServer ? options.encryptedSecret : false,
1529
                options.storeDataOnServer ? options.recoverySecret : false,
1530
                checksum,
1531
                keyIndex,
1532
                options.support_secret || null,
1533
                options.segwit || null
1534
            )
1535
                .then(
1536
                    // result, deferred, self(apiclient)
1537
                    function(result) {
1538
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1539
1540
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1541
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1542
                        });
1543
1544
                        var wallet = new Wallet(
1545
                            self,
1546
                            options.identifier,
1547
                            Wallet.WALLET_VERSION_V3,
1548
                            null,
1549
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1550
                            options.storeDataOnServer ? options.encryptedSecret : null,
1551
                            {keyIndex: options.primaryPublicKey},
1552
                            options.backupPublicKey,
1553
                            blocktrailPublicKeys,
1554
                            keyIndex,
1555
                            result.segwit || 0,
1556
                            self.testnet,
1557
                            checksum,
1558
                            result.upgrade_key_index,
1559
                            options.useCashAddress,
1560
                            options.bypassNewAddressCheck
1561
                        );
1562
1563
                        // pass along decrypted data to avoid extra work
1564
                        return wallet.unlock({
1565
                            walletVersion: Wallet.WALLET_VERSION_V3,
1566
                            passphrase: options.passphrase,
1567
                            primarySeed: options.primarySeed,
1568
                            secret: options.secret
1569
                        }).then(function() {
1570
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1571
                            return [
1572
                                wallet,
1573
                                {
1574
                                    walletVersion: wallet.walletVersion,
1575
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1576
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1577
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1578
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1579
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1580
                                    blocktrailPublicKeys: blocktrailPublicKeys
1581
                                }
1582
                            ];
1583
                        });
1584
                    }
1585
                );
1586
        })
1587
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1588
1589
    return deferred.promise;
1590
};
1591
1592
function verifyPublicBip32Key(bip32Key, network) {
1593
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1594
    if (typeof hk.keyPair.d !== "undefined") {
1595
        throw new Error('BIP32Key contained private key material - abort');
1596
    }
1597
1598
    if (bip32Key[1].slice(0, 1) !== "M") {
1599
        throw new Error("BIP32Key contained non-public path - abort");
1600
    }
1601
}
1602
1603
function verifyPublicOnly(walletData, network) {
1604
    verifyPublicBip32Key(walletData.primary_public_key, network);
1605
    verifyPublicBip32Key(walletData.backup_public_key, network);
1606
}
1607
1608
/**
1609
 * create wallet using the API
1610
 *
1611
 * @param identifier            string      the wallet identifier to create
1612
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1613
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1614
 * @param primaryMnemonic       string      mnemonic to store
1615
 * @param checksum              string      checksum to store
1616
 * @param keyIndex              int         keyIndex that was used to create wallet
1617
 * @param segwit                bool
1618
 * @returns {q.Promise}
1619
 */
1620
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1621
                                                checksum, keyIndex, segwit) {
1622
    var self = this;
1623
1624
    var postData = {
1625
        identifier: identifier,
1626
        wallet_version: Wallet.WALLET_VERSION_V1,
1627
        primary_public_key: primaryPublicKey,
1628
        backup_public_key: backupPublicKey,
1629
        primary_mnemonic: primaryMnemonic,
1630
        checksum: checksum,
1631
        key_index: keyIndex,
1632
        segwit: segwit
1633
    };
1634
1635
    verifyPublicOnly(postData, self.network);
1636
1637
    return self.blocktrailClient.post("/wallet", null, postData);
1638
};
1639
1640
/**
1641
 * create wallet using the API
1642
 *
1643
 * @param identifier            string      the wallet identifier to create
1644
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1645
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1646
 * @param encryptedPrimarySeed  string      openssl format
1647
 * @param encryptedSecret       string      openssl format
1648
 * @param recoverySecret        string      openssl format
1649
 * @param checksum              string      checksum to store
1650
 * @param keyIndex              int         keyIndex that was used to create wallet
1651
 * @param supportSecret         string
1652
 * @param segwit                bool
1653
 * @returns {q.Promise}
1654
 */
1655
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1656
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1657
    var self = this;
1658
1659
    var postData = {
1660
        identifier: identifier,
1661
        wallet_version: Wallet.WALLET_VERSION_V2,
1662
        primary_public_key: primaryPublicKey,
1663
        backup_public_key: backupPublicKey,
1664
        encrypted_primary_seed: encryptedPrimarySeed,
1665
        encrypted_secret: encryptedSecret,
1666
        recovery_secret: recoverySecret,
1667
        checksum: checksum,
1668
        key_index: keyIndex,
1669
        support_secret: supportSecret || null,
1670
        segwit: segwit
1671
    };
1672
1673
    verifyPublicOnly(postData, self.network);
1674
1675
    return self.blocktrailClient.post("/wallet", null, postData);
1676
};
1677
1678
/**
1679
 * create wallet using the API
1680
 *
1681
 * @param identifier            string      the wallet identifier to create
1682
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1683
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1684
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1685
 * @param encryptedSecret       Buffer      buffer of ciphertext
1686
 * @param recoverySecret        Buffer      buffer of recovery secret
1687
 * @param checksum              string      checksum to store
1688
 * @param keyIndex              int         keyIndex that was used to create wallet
1689
 * @param supportSecret         string
1690
 * @param segwit                bool
1691
 * @returns {q.Promise}
1692
 */
1693
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1694
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1695
    var self = this;
1696
1697
    var postData = {
1698
        identifier: identifier,
1699
        wallet_version: Wallet.WALLET_VERSION_V3,
1700
        primary_public_key: primaryPublicKey,
1701
        backup_public_key: backupPublicKey,
1702
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1703
        encrypted_secret: encryptedSecret.toString('base64'),
1704
        recovery_secret: recoverySecret.toString('hex'),
1705
        checksum: checksum,
1706
        key_index: keyIndex,
1707
        support_secret: supportSecret || null,
1708
        segwit: segwit
1709
    };
1710
1711
    verifyPublicOnly(postData, self.network);
1712
1713
    return self.blocktrailClient.post("/wallet", null, postData);
1714
};
1715
1716
/**
1717
 * create wallet using the API
1718
 *
1719
 * @param identifier            string      the wallet identifier to create
1720
 * @param postData              object
1721
 * @param [cb]                  function    callback(err, result)
1722
 * @returns {q.Promise}
1723
 */
1724
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1725
    var self = this;
1726
1727
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1728
};
1729
1730
/**
1731
 * upgrade wallet to use a new account number
1732
 *  the account number specifies which blocktrail cosigning key is used
1733
 *
1734
 * @param identifier            string      the wallet identifier
1735
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1736
 * @param keyIndex              int         keyIndex that was used to create wallet
1737
 * @param [cb]                  function    callback(err, result)
1738
 * @returns {q.Promise}
1739
 */
1740
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1741
    var self = this;
1742
1743
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1744
        key_index: keyIndex,
1745
        primary_public_key: primaryPublicKey
1746
    }, cb);
1747
};
1748
1749
/**
1750
 * get the balance for the wallet
1751
 *
1752
 * @param identifier            string      the wallet identifier
1753
 * @param [cb]                  function    callback(err, result)
1754
 * @returns {q.Promise}
1755
 */
1756
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1757
    var self = this;
1758
1759
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1760
};
1761
1762
/**
1763
 * do HD wallet discovery for the wallet
1764
 *
1765
 * @param identifier            string      the wallet identifier
1766
 * @param [cb]                  function    callback(err, result)
1767
 * @returns {q.Promise}
1768
 */
1769
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1770
    var self = this;
1771
1772
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1773
};
1774
1775
1776
/**
1777
 * get a new derivation number for specified parent path
1778
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1779
 *
1780
 * @param identifier            string      the wallet identifier
1781
 * @param path                  string      the parent path for which to get a new derivation,
1782
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1783
 * @param [cb]                  function    callback(err, result)
1784
 * @returns {q.Promise}
1785
 */
1786
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1787
    var self = this;
1788
1789
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1790
};
1791
1792
1793
/**
1794
 * delete the wallet
1795
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1796
 *  is required to be able to delete a wallet
1797
 *
1798
 * @param identifier            string      the wallet identifier
1799
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1800
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1801
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1802
 * @param [cb]                  function    callback(err, result)
1803
 * @returns {q.Promise}
1804
 */
1805
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1806
    var self = this;
1807
1808
    if (typeof force === "function") {
1809
        cb = force;
1810
        force = false;
1811
    }
1812
1813
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1814
        checksum: checksumAddress,
1815
        signature: checksumSignature
1816
    }, cb);
1817
};
1818
1819
/**
1820
 * use the API to get the best inputs to use based on the outputs
1821
 *
1822
 * the return array has the following format:
1823
 * [
1824
 *  "utxos" => [
1825
 *      [
1826
 *          "hash" => "<txHash>",
1827
 *          "idx" => "<index of the output of that <txHash>",
1828
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1829
 *          "value" => 32746327,
1830
 *          "address" => "1address",
1831
 *          "path" => "m/44'/1'/0'/0/13",
1832
 *          "redeem_script" => "<redeemScript-hex>",
1833
 *      ],
1834
 *  ],
1835
 *  "fee"   => 10000,
1836
 *  "change"=> 1010109201,
1837
 * ]
1838
 *
1839
 * @param identifier        string      the wallet identifier
1840
 * @param pay               array       {'address': (int)value}     coins to send
1841
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1842
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1843
 * @param feeStrategy       string      defaults to
1844
 * @param options
1845
 * @param [cb]              function    callback(err, utxos, fee, change)
1846
 * @returns {q.Promise}
1847
 */
1848
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1849
    var self = this;
1850
1851
    if (typeof feeStrategy === "function") {
1852
        cb = feeStrategy;
1853
        feeStrategy = null;
1854
        options = {};
1855
    } else if (typeof options === "function") {
1856
        cb = options;
1857
        options = {};
1858
    }
1859
1860
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1861
    options = options || {};
1862
1863
    var deferred = q.defer();
1864
    deferred.promise.spreadNodeify(cb);
1865
1866
    var params = {
1867
        lock: lockUTXO,
1868
        zeroconf: allowZeroConf ? 1 : 0,
1869
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1870
        fee_strategy: feeStrategy
1871
    };
1872
1873
    if (options.forcefee) {
1874
        params['forcefee'] = options.forcefee;
1875
    }
1876
1877
    deferred.resolve(
1878
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1879
            function(result) {
1880
                return [result.utxos, result.fee, result.change, result];
1881
            },
1882
            function(err) {
1883
                if (err.message.match(/too low to pay the fee/)) {
1884
                    throw blocktrail.WalletFeeError(err);
1885
                }
1886
1887
                throw err;
1888
            }
1889
        )
1890
    );
1891
1892
    return deferred.promise;
1893
};
1894
1895
/**
1896
 * @param [cb]              function    callback(err, utxos, fee, change)
1897
 * @returns {q.Promise}
1898
 */
1899
APIClient.prototype.feePerKB = function(cb) {
1900
    var self = this;
1901
1902
    var deferred = q.defer();
1903
    deferred.promise.spreadNodeify(cb);
1904
1905
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
1906
1907
    return deferred.promise;
1908
};
1909
1910
/**
1911
 * send the transaction using the API
1912
 *
1913
 * @param identifier        string      the wallet identifier
1914
 * @param txHex             string      partially signed transaction as hex string
1915
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1916
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1917
 * @param [twoFactorToken]  string      2FA token
1918
 * @param [prioboost]       bool
1919
 * @param [cb]              function    callback(err, txHash)
1920
 * @returns {q.Promise}
1921
 */
1922
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1923
    var self = this;
1924
1925
    if (typeof twoFactorToken === "function") {
1926
        cb = twoFactorToken;
1927
        twoFactorToken = null;
1928
        prioboost = false;
1929
    } else if (typeof prioboost === "function") {
1930
        cb = prioboost;
1931
        prioboost = false;
1932
    }
1933
1934
    var data = {
1935
        paths: paths,
1936
        two_factor_token: twoFactorToken
1937
    };
1938
    if (typeof txHex === "string") {
1939
        data.raw_transaction = txHex;
1940
    } else if (typeof txHex === "object") {
1941
        Object.keys(txHex).map(function(key) {
1942
            data[key] = txHex[key];
1943
        });
1944
    }
1945
1946
    return self.blocktrailClient.post(
1947
        "/wallet/" + identifier + "/send",
1948
        {
1949
            check_fee: checkFee ? 1 : 0,
1950
            prioboost: prioboost ? 1 : 0
1951
        },
1952
        data,
1953
        cb
1954
    );
1955
};
1956
1957
/**
1958
 * setup a webhook for this wallet
1959
 *
1960
 * @param identifier        string      the wallet identifier
1961
 * @param webhookIdentifier string      identifier for the webhook
1962
 * @param url               string      URL to receive webhook events
1963
 * @param [cb]              function    callback(err, webhook)
1964
 * @returns {q.Promise}
1965
 */
1966
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
1967
    var self = this;
1968
1969
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
1970
};
1971
1972
/**
1973
 * delete a webhook that was created for this wallet
1974
 *
1975
 * @param identifier        string      the wallet identifier
1976
 * @param webhookIdentifier string      identifier for the webhook
1977
 * @param [cb]              function    callback(err, success)
1978
 * @returns {q.Promise}
1979
 */
1980
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
1981
    var self = this;
1982
1983
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
1984
};
1985
1986
/**
1987
 * get all transactions for an wallet (paginated)
1988
 *
1989
 * @param identifier    string      wallet identifier
1990
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1991
 * @param [cb]          function    callback function to call when request is complete
1992
 * @return q.Promise
1993
 */
1994
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
1995
    var self = this;
1996
1997
    if (typeof params === "function") {
1998
        cb = params;
1999
        params = null;
2000
    }
2001
2002
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2003
};
2004
2005
/**
2006
 * get all addresses for an wallet (paginated)
2007
 *
2008
 * @param identifier    string      wallet identifier
2009
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2010
 * @param [cb]          function    callback function to call when request is complete
2011
 * @return q.Promise
2012
 */
2013
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2014
    var self = this;
2015
2016
    if (typeof params === "function") {
2017
        cb = params;
2018
        params = null;
2019
    }
2020
2021
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2022
};
2023
2024
/**
2025
 * @param identifier    string      wallet identifier
2026
 * @param address       string      the address to label
2027
 * @param label         string      the label
2028
 * @param [cb]          function    callback(err, res)
2029
 * @return q.Promise
2030
 */
2031
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2032
    var self = this;
2033
2034
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2035
};
2036
2037
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2038
    var self = this;
2039
2040
    if (typeof feeStrategy === "function") {
2041
        cb = feeStrategy;
2042
        feeStrategy = null;
2043
    } else if (typeof options === "function") {
2044
        cb = options;
2045
        options = {};
2046
    }
2047
2048
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2049
    options = options || {};
2050
2051
    var params = {
2052
        outputs: options.outputs ? options.outputs : 1,
2053
        zeroconf: allowZeroConf ? 1 : 0,
2054
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2055
        fee_strategy: feeStrategy
2056
    };
2057
2058
    if (options.forcefee) {
2059
        params['forcefee'] = options.forcefee;
2060
    }
2061
2062
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2063
};
2064
2065
/**
2066
 * get all UTXOs for an wallet (paginated)
2067
 *
2068
 * @param identifier    string      wallet identifier
2069
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2070
 * @param [cb]          function    callback function to call when request is complete
2071
 * @return q.Promise
2072
 */
2073
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2074
    var self = this;
2075
2076
    if (typeof params === "function") {
2077
        cb = params;
2078
        params = null;
2079
    }
2080
2081
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2082
};
2083
2084
/**
2085
 * get a paginated list of all wallets associated with the api user
2086
 *
2087
 * @param [params]      object      pagination: {page: 1, limit: 20}
2088
 * @param [cb]          function    callback function to call when request is complete
2089
 * @return q.Promise
2090
 */
2091
APIClient.prototype.allWallets = function(params, cb) {
2092
    var self = this;
2093
2094
    if (typeof params === "function") {
2095
        cb = params;
2096
        params = null;
2097
    }
2098
2099
    return self.blocktrailClient.get("/wallets", params, true, cb);
2100
};
2101
2102
/**
2103
 * verify a message signed bitcoin-core style
2104
 *
2105
 * @param message        string
2106
 * @param address        string
2107
 * @param signature      string
2108
 * @param [cb]          function    callback function to call when request is complete
2109
 * @return q.Promise
2110
 */
2111
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2112
    var self = this;
2113
2114
    var deferred = q.defer();
2115
    deferred.promise.nodeify(cb);
2116
    try {
2117
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2118
        deferred.resolve(result);
2119
    } catch (e) {
2120
        deferred.reject(e);
2121
    }
2122
2123
    return deferred.promise;
2124
};
2125
2126
/**
2127
 * max is 0.001
2128
 * testnet only
2129
 *
2130
 * @param address
2131
 * @param amount
2132
 * @param cb
2133
 */
2134
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2135
    var self = this;
2136
2137
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2138
};
2139
2140
/**
2141
 * send a raw transaction
2142
 *
2143
 * @param rawTransaction    string      raw transaction as HEX
2144
 * @param [cb]              function    callback function to call when request is complete
2145
 * @return q.Promise
2146
 */
2147
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2148
    var self = this;
2149
2150
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2151
};
2152
2153
/**
2154
 * get the current price index
2155
 *
2156
 * @param [cb]          function    callback({'USD': 287.30})
2157
 * @return q.Promise
2158
 */
2159
APIClient.prototype.price = function(cb) {
2160
    var self = this;
2161
2162
    return self.blocktrailClient.get("/price", null, false, cb);
2163
};
2164
2165
module.exports = APIClient;
2166